﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.Reflection;
using System.Linq;
using System.Windows;
#if WPF
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Shapes;
using System.Windows.Media.Animation;

#else
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Media;
using Windows.UI;
using Windows.Foundation;
using Windows.UI.Xaml.Data;
using Windows.UI.Xaml.Shapes;
using Windows.UI.Xaml.Media.Animation;
#endif

namespace Eternal.DataVisualization
{
    public abstract class Series : Control
    {

        public ChartLabel Label
        {
            get { return (ChartLabel)GetValue(LabelProperty); }
            set { SetValue(LabelProperty, value); }
        }

        // Using a DependencyProperty as the backing store for Label.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty LabelProperty =
            DependencyProperty.Register("Label", typeof(ChartLabel), typeof(Series), new PropertyMetadata(null));

        internal bool PointsGenerated, IsRangeUpdateNeeded;

        internal Chart Area { get; set; }

        protected Canvas RootPanel { get; set; }

        internal Canvas SegmentPanel { get; set; }

        protected double MinimumX, MaximumX, MinimumY, MaximumY, DeltaX, DeltaY;

        internal XAxis XAxis;

        internal YAxis YAxis;

        public ControlTemplate LegendTemplate
        {
            get { return (ControlTemplate)GetValue(LegendTemplateProperty); }
            set { SetValue(LegendTemplateProperty, value); }
        }

        // Using a DependencyProperty as the backing store for LegendTemplate.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty LegendTemplateProperty =
            DependencyProperty.Register("LegendTemplate", typeof(ControlTemplate), typeof(Series), new PropertyMetadata(null));

        public object LegendContent
        {
            get { return GetValue(LegendContentProperty); }
            set { SetValue(LegendContentProperty, value); }
        }

        // Using a DependencyProperty as the backing store for LegendContent.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty LegendContentProperty =
            DependencyProperty.Register("LegendContent", typeof(object), typeof(Series), new PropertyMetadata(null));

        public string AdditionalPath
        {
            get { return (string)GetValue(AdditionalPathProperty); }
            set { SetValue(AdditionalPathProperty, value); }
        }

        public DataTemplate PointsInfoTemplate
        {
            get { return (DataTemplate)GetValue(PointsInfoTemplateProperty); }
            set { SetValue(PointsInfoTemplateProperty, value); }
        }

        // Using a DependencyProperty as the backing store for PointsInfoTemplate.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty PointsInfoTemplateProperty =
            DependencyProperty.Register("PointsInfoTemplate", typeof(DataTemplate), typeof(Series),
                                        new PropertyMetadata(null));

        // Using a DependencyProperty as the backing store for AdditionalPath.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty AdditionalPathProperty =
            DependencyProperty.Register("AdditionalPath", typeof(string), typeof(Series), new PropertyMetadata(string.Empty));

        public Brush SegmentBrush
        {
            get { return (Brush)GetValue(SegmentBrushProperty); }
            set { SetValue(SegmentBrushProperty, value); }
        }

        // Using a DependencyProperty as the backing store for SegmentBrush.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty SegmentBrushProperty =
            DependencyProperty.Register("SegmentBrush", typeof(Brush), typeof(Series), new PropertyMetadata(null));

        protected List<object> AdditionalPathData { get; set; }

        internal protected List<object> ActualXData;

        internal Canvas AdditionalPanel { get; set; }

        protected double AvailableWidth, AvailableHeight;

        protected IList<double> ActualYData;

        public IList<SegmentInfo> SegmentInfos { get; set; }

        public IEnumerable ItemsSource
        {
            get { return (IEnumerable)GetValue(ItemsSourceProperty); }
            set { SetValue(ItemsSourceProperty, value); }
        }

        // Using a DependencyProperty as the backing store for ItemsSource.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty ItemsSourceProperty =
            DependencyProperty.Register("ItemsSource", typeof(IEnumerable), typeof(Series),
                                        new PropertyMetadata(null, OnItemsSourceChanged));

        public bool ShowDataPoints
        {
            get { return (bool)GetValue(ShowDataPointsProperty); }
            set { SetValue(ShowDataPointsProperty, value); }
        }

        // Using a DependencyProperty as the backing store for ShowDataPoints.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty ShowDataPointsProperty =
            DependencyProperty.Register("ShowDataPoints", typeof(bool), typeof(Series), new PropertyMetadata(false));

        public double StrokeThickness
        {
            get { return (double)GetValue(StrokeThicknessProperty); }
            set { SetValue(StrokeThicknessProperty, value); }
        }

        // Using a DependencyProperty as the backing store for StrokeThickness.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty StrokeThicknessProperty =
            DependencyProperty.Register("StrokeThickness", typeof(double), typeof(Series), new PropertyMetadata(2d));

        public string PrimaryPath
        {
            get { return (string)GetValue(PrimaryPathProperty); }
            set { SetValue(PrimaryPathProperty, value); }
        }

        // Using a DependencyProperty as the backing store for PrimaryPath.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty PrimaryPathProperty =
            DependencyProperty.Register("PrimaryPath", typeof(string), typeof(Series), new PropertyMetadata(string.Empty));

        public string SecondaryPath
        {
            get { return (string)GetValue(SecondaryPathProperty); }
            set { SetValue(SecondaryPathProperty, value); }
        }

        // Using a DependencyProperty as the backing store for SecondaryPath.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty SecondaryPathProperty =
            DependencyProperty.Register("SecondaryPath", typeof(string), typeof(Series), new PropertyMetadata(string.Empty));

        private static void OnItemsSourceChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            ((Series)d).OnItemsSourceChanged(e);
        }

        private void OnItemsSourceChanged(DependencyPropertyChangedEventArgs e)
        {
            if (e.OldValue is INotifyCollectionChanged)
            {
                (e.OldValue as INotifyCollectionChanged).CollectionChanged -= OnPointCollectionChanged;
            }

            if (e.NewValue is INotifyCollectionChanged)
            {
                (e.NewValue as INotifyCollectionChanged).CollectionChanged += OnPointCollectionChanged;
            }

            PointsGenerated = false;

            GenerateDatas();

            if (Area != null)
                Area.DynamicInvalidate();
        }

        internal string YDataType;
        internal string XDataType;


        private void OnPointCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
        {
            if (ActualXData == null)
            {
                ActualXData = new List<object>();
                ActualYData = new List<double>();
            }
            if (e.Action == NotifyCollectionChangedAction.Add)
            {
                var index = e.NewStartingIndex;
                if (!string.IsNullOrEmpty(PrimaryPath))
                {
                    ActualXData.Insert(index, AddNewItems(e.NewItems[0], PrimaryPath));
                    XDataType = DataIterator.SetDataType(ActualXData[index]);
                }
                if (!string.IsNullOrEmpty(SecondaryPath))
                {
                    ActualYData.Insert(index, Convert.ToDouble(AddNewItems(e.NewItems[0], SecondaryPath)));
                    YDataType = DataIterator.SetDataType(ActualYData[index]);
                }
                if (!string.IsNullOrEmpty(AdditionalPath))
                    AdditionalPathData.Insert(e.NewStartingIndex, AddNewItems(e.NewItems[0], AdditionalPath));
            }
            else if (e.Action == NotifyCollectionChangedAction.Remove)
            {
                RemoveItem(e.OldStartingIndex);
            }
            IsRangeUpdateNeeded = true;
            if (Area != null)
                Area.DynamicInvalidate();
        }

        private object AddNewItems(object item, string path)
        {
#if WPF
            var propertInfo = item.GetType().GetProperty(path);
#else
            var propertInfo = item.GetType().GetRuntimeProperty(path);
#endif
            var fastPropertInfo = new FastProperty(propertInfo);
            return (fastPropertInfo.Get(item));
        }

        private void RemoveItem(int index)
        {
            ActualXData.RemoveAt(index);
            ActualYData.RemoveAt(index);
        }

        public Series()
        {
            DefaultStyleKey = typeof(Series);
        }

        internal void InvalidateData()
        {
            if (!PointsGenerated)
                GenerateDatas();

            if (IsRangeUpdateNeeded)
                UpdateRange();
            if (XAxis != null && YAxis != null)
            {
                XAxis.Type = XDataType;
                YAxis.Type = YDataType;
                XAxis.ActualData = ActualXData;
            }
            //MinMaxChange();
        }

        protected virtual void MinMaxChange()
        {
        }

        internal virtual void GenerateDatas()
        {
            if (ItemsSource != null && !String.IsNullOrEmpty(PrimaryPath))
            {
                var type = "";
                ActualXData = DataIterator.Iterate(ItemsSource, PrimaryPath, ref type);
                XDataType = type;
                ActualYData = DataIterator.IterateDoubleValue(ItemsSource, SecondaryPath, ref type);
                YDataType = type;
                //if (!String.IsNullOrEmpty(AdditionalPath))
                //{
                //    string[] addPath = { AdditionalPath };
                //    AdditionalPathData = DataIterator.Iterate(ItemsSource, addPath)[0];
                //}

                PointsGenerated = true;
                IsRangeUpdateNeeded = true;
            }
        }

        internal virtual void UpdateRange()
        {
            if (PointsGenerated && ActualXData != null)
            {
                MinimumX = 0;
                MaximumX = ActualXData.Count - 1;
                DeltaX = MaximumX - MinimumX;
                MaximumY = Convert.ToDouble(ActualYData.Max());
                MinimumY = Convert.ToDouble(ActualYData.Min());
                DeltaY = MaximumY - MinimumY;
                DeltaY = DeltaY == 0 ? 1 : DeltaY;
                DeltaX = DeltaX == 0 ? 1 : DeltaX;

                if (XAxis != null && YAxis != null)
                {
                    YAxis.ActualEnd = Math.Max(YAxis.ActualEnd, MaximumY);
                    YAxis.ActualStart = Math.Min(YAxis.ActualStart, MinimumY);
                    XAxis.ActualEnd = Math.Max(XAxis.ActualEnd, MaximumX);
                    XAxis.ActualStart = Math.Min(XAxis.ActualStart, MinimumX);
                }

                IsRangeUpdateNeeded = false;
            }
        }

        public Point CoordinateToValue(Point coordiante)
        {
            var currPoint = new Point((int)Math.Round(MinimumX + MaximumX * (coordiante.X / AvailableWidth)), 0);
            var x = (int)currPoint.X;
            if (x < 0 && x <= ActualXData.Count)
                x = 0;
            return new Point(x, Convert.ToDouble(ActualYData[x]));
        }

        public double ValueToCoordianteXAxis(double value)
        {
            if (XAxis == null || YAxis == null) return 0;
            return Area == null ? Math.Round(AvailableWidth * (value - MinimumX) / DeltaX) : Area.ValueToCoordianteXAxis(XAxis.Start, XAxis.End, value, AvailableWidth);
        }

        public double ValueToCoordianteYAxis(double value)
        {
            if (XAxis == null || YAxis == null) return 0;
            if (Area == null)
                return Math.Round(AvailableHeight * (1 - (value - MinimumY) / DeltaY));
            else
                return Area.ValueToCoordianteYAxis(YAxis.Start, YAxis.End, value, AvailableHeight);
        }

        //public Point ValueToCoordiante(Point value)
        //{
        //    if (Area == null)
        //        return new Point(Math.Round(AvailableWidth * (value.X - MinimumX) / DeltaX),
        //                         Math.Round(AvailableHeight * (1 - (value.Y - MinimumY) / DeltaY)));
        //    else
        //        return Area.ValueToCoordiante(value);
        //}

        //protected override Size MeasureOverride(Size constraint)
        //{
        //    if (!double.IsInfinity(constraint.Height) && !double.IsInfinity(constraint.Width))
        //    {
        //        if (AvailableHeight != constraint.Height || AvailableWidth != constraint.Width)
        //        {
        //            this.AvailableHeight = constraint.Height;
        //            this.AvailableWidth = constraint.Width;
        //            DrawSegment();
        //        }
        //    }
        //    return base.MeasureOverride(constraint);
        //}

        internal protected virtual void ApplyBrush(Shape element, int index)
        {
            element.StrokeThickness = StrokeThickness;
            if (SegmentBrush != null)
            {
                element.Fill = SegmentBrush;
            }
            else
            {
                element.Fill = Area.GetBrush(index);
            }
        }

        internal void DrawSegment()
        {
            if (!PointsGenerated || ActualXData == null) return;
            if (this is AxisType && XAxis != null && YAxis != null && ActualXData != null)
            {
                MinimumX = XAxis.Start;
                MaximumX = XAxis.End;

                MinimumY = YAxis.Start;
                MaximumY = YAxis.End;

                AvailableWidth = ActualWidth;
                AvailableHeight = ActualHeight;

                Clip = new RectangleGeometry { Rect = new Rect(0, 0, AvailableWidth, AvailableHeight) };

                if (Label != null)
                {
                    Label.Positions.Clear();
                }

                OnDrawSegment();
            }
            else if (this is NonAxisType)
            {
                AvailableWidth = ActualWidth;
                AvailableHeight = ActualHeight;

                Clip = new RectangleGeometry { Rect = new Rect(0, 0, AvailableWidth, AvailableHeight) };

                if (Label != null)
                {
                    Label.Positions.Clear();
                }

                OnDrawSegment();
            }

            if (Label != null)
                Label.UpdateLabels(SegmentPanel);
        }

        protected internal virtual void OnDrawSegment()
        {
        }

#if WPF

        protected override void OnMouseMove(MouseEventArgs e)
        {
            base.OnMouseMove(e);
        }

#else
        protected override void OnPointerMoved(Windows.UI.Xaml.Input.PointerRoutedEventArgs e)
        {
            base.OnPointerMoved(e);
        }

#endif

#if WPF
        public override void OnApplyTemplate()
#else
        protected override void OnApplyTemplate()
#endif
        {
            AdditionalPanel = new Canvas();
            RootPanel = GetTemplateChild("PART_RootPanel") as Canvas;
            SegmentInfos = new List<SegmentInfo>();
            SegmentPanel = new Canvas();
            if (RootPanel != null)
            {
                RootPanel.Children.Add(SegmentPanel);
                RootPanel.Children.Add(AdditionalPanel);
            }
            base.OnApplyTemplate();
        }
    }

    public static class ChartAnimation
    {
        public static void AnimateRectangle(List<Rectangle> rectangles)
        {
            var storyBoard = new Storyboard();
            foreach (var rectangle in rectangles)
            {
                rectangle.RenderTransform = new ScaleTransform();
                rectangle.RenderTransformOrigin = new Point(1, 1);
                var top = Canvas.GetTop(rectangle);
                var height = rectangle.Height;

                var animation = new DoubleAnimation
                {
                    From = 0,
                    To = 1
                };
                Storyboard.SetTarget(animation, rectangle);
#if WPF
                Storyboard.SetTargetProperty(animation,new PropertyPath("(UIElement.RenderTransform).(ScaleTransform.ScaleY)"));
#else
                Storyboard.SetTargetProperty(animation, "(UIElement.RenderTransform).(ScaleTransform.ScaleY)");
#endif
                storyBoard.Children.Add(animation);
            }
            storyBoard.Begin();
        }
    }
}
